//----------------------------------------------------------------------------
//  ,-.
// /_\___ ,   Ninja Framework
// '  ___\    (C) Riccardo Capra - http://riccar.do
//  //   \\
//----------------------------------------------------------------------------
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
//----------------------------------------------------------------------------

#ifndef NJ_TEST_UNIT_H
#	define NJ_TEST_UNIT_H

#	include "NjExample01.h"
#	include "NjHighResolutionTimer.h"

	/// \brief A support class to implement test unit macros
	class NjTestUnit
	{
	public:
		/// \brief Returns NJ_TRUE if all the tests were successful
		static NjBool IsUnitTestSuccess();

		/// \brief Flushes the buffer of messages to be sent to console
		///
		/// This function should be called before application shutdown
		static void Flush();

		/// \brief Internal class used by macro NJ_TEST_UNITS_GROUP
		class TestUnitsGroup
		{
			friend class NjTestUnit;

		protected:
			/// \brief Declares a new NJ_TEST_UNITS_GROUP
			TestUnitsGroup(NjChar const * szMessage);

			/// \brief Ends this NJ_TEST_UNITS_GROUP
			~TestUnitsGroup();

			/// \brief Internal class used by macro NJ_TEST_UNIT
			class TestUnit
			{
				friend class NjTestUnit;

			protected:
				/// \brief Declares a new NJ_TEST_UNIT
				TestUnit(TestUnitsGroup * pGroup, NjChar const * szMessage);

				/// \brief Ends this NJ_TEST_UNIT
				~TestUnit();

				/// \brief Marks this test unit as failed if \em bCondition is NJ_FALSE
				/// \param bCondition The condition that if NJ_FALSE will trigger the assertion
				/// \param szCondition The condition converted to string
				/// \param szMessage A custom message to be displayed when the assert is triggered
				/// \param szFile The C++ file that triggered the assert
				/// \param uLine The C++ file line that triggered the assert
				/// \param szFunction The C++ function/method that triggered the assert
				/// \return The value of \em bCondition
				NjBool Test(NjBool bCondition, NjChar const * szCondition, NjChar const * szMessage, NjChar const * szFile, NjUint uLine, NjChar const * szFunction);

			private:
				/// \brief NJ_TRUE if this test unit was successful
				NjBool m_bSuccess;

				NJ_DISABLE_DEFAULT_CONSTRUCTOR(TestUnit);
				NJ_DISABLE_COPY(TestUnit);
			};

		private:
			NJ_DISABLE_DEFAULT_CONSTRUCTOR(TestUnitsGroup);
			NJ_DISABLE_COPY(TestUnitsGroup);
		};

	protected:
		NJ_DISABLE_INSTANCE(NjTestUnit);

		/// \brief Private class for internal use
		class Static;

		/// \brief The last message printed out
		static NjChar const * ms_szLastTestUnit;

		/// \brief Set to NJ_FALSE if at least a test unit failed
		static NjBool ms_bUnitTestSuccess;

		/// \brief The total number of successful tests
		static NjUint ms_uSuccessTests;

		/// \brief The total number of performed tests
		static NjUint ms_uTotTests;
	};

/// \copydoc NJ_TEST_UNITS_GROUP
/// \param tId A uinque id
#	define	_NJ_TEST_UNITS_GROUP(tId, szMessage)				\
	{															\
		class tId : public NjTestUnit::TestUnitsGroup			\
		{														\
		public:													\
			NJ_TRIVIAL_DESTRUCTOR(tId);							\
																\
			inline tId()										\
			:													\
				NjTestUnit::TestUnitsGroup(szMessage)			\
			{

/// \copydoc NJ_TEST_UNITS_GROUP_END
/// \param tId A uinque id
#	define	_NJ_TEST_UNITS_GROUP_END(tId)						\
			}													\
		}	tId;												\
	}

/// \brief Declares a new test unit group
///
/// If more than one consecutive test unit group has message \em szMessage , the test unit groups will be merged together
/// \param szMessage A message to be displayed when the test unit group will start
#	define	NJ_TEST_UNITS_GROUP(szMessage) _NJ_TEST_UNITS_GROUP(NJ_UNIQUE_ID(TestsUnitGroup), szMessage)

/// \brief Ends the test unit group declaration
#	define	NJ_TEST_UNITS_GROUP_END() _NJ_TEST_UNITS_GROUP_END(NJ_UNIQUE_ID(kTestsUnitGroupInstance))

/// \copydoc NJ_TEST_UNIT
/// \param tId A uinque id
#	define	_NJ_TEST_UNIT(tId, szMessage)					\
	{														\
		class tId : public TestUnit							\
		{													\
		public:												\
			NJ_TRIVIAL_DESTRUCTOR(tId);						\
															\
			inline tId(TestUnitsGroup * pTestUnitsGroup)	\
			:												\
				TestUnit(pTestUnitsGroup, szMessage)		\
			{

/// \copydoc NJ_TEST_UNIT_END
/// \param tId A uinque id
#	define	_NJ_TEST_UNIT_END(tId)							\
			}												\
		}	tId(this);										\
	}

/// \brief Declares a new test unit
/// \param szMessage A message to be displayed when the test unit group will start
#	define	NJ_TEST_UNIT(szMessage) _NJ_TEST_UNIT(NJ_UNIQUE_ID(TestUnit), szMessage)

/// \brief Ends the test unit declaration
#	define	NJ_TEST_UNIT_END() _NJ_TEST_UNIT_END(NJ_UNIQUE_ID(kTestUnitInstance))

/// \brief The test will fail if \em bCondition is NJ_FALSE
///
/// Test will be immediately ended without continuing further
/// \param bCondition The condition that if NJ_FALSE will make the test fail
/// \param szCondition The condition converted to string
/// \param szMessage A custom message to be displayed when the test will fail
#	define	_NJ_TEST_MSG(bCondition, szCondition, szMessage) { if(!TestUnit::Test(bCondition, szCondition, szMessage, __FILE__, __LINE__, __FUNCTION__)) return; }

/// \brief The test will fail if \em bCondition is NJ_FALSE
///
/// Test will be immediately ended without continuing further
/// \param bCondition The condition that if NJ_FALSE will make the test fail
#	define	NJ_TEST(bCondition) NJ_TEST_MSG(bCondition, NJ_NULL)

/// \brief The test will fail if \em bCondition is NJ_FALSE
///
/// Test will be immediately ended without continuing further
/// \param bCondition The condition that if NJ_FALSE will make the test fail
/// \param szMessage A custom message to be displayed when the test will fail
#	define	NJ_TEST_MSG(bCondition, szMessage) _NJ_TEST_MSG(bCondition, NJ_TO_STRING8(bCondition), szMessage)

/// \brief The test will be forced to fail
///
/// Test will be immediately ended without continuing further
#	define	NJ_TEST_FAIL() NJ_TEST_FAIL_MSG(NJ_NULL)

/// \brief The test will be forced to fail
///
/// Test will be immediately ended without continuing further
/// \param szMessage A custom message to be displayed when the test will fail
#	define	NJ_TEST_FAIL_MSG(szMessage) _NJ_TEST_MSG(NJ_FALSE, NJ_NULL, szMessage)

#	include "NjTestUnit_inline.h"

#endif // NJ_TEST_UNIT_H
